#ifndef __LIGHT_H__
#define __LIGHT_H__
#include "vec3.h"
#include "vec4.h"
#include <utility>

class Light {
public:
    Light(const Color4f& lightColor)
        : mLightColor(lightColor) {}
    virtual std::pair<Vec3f, Color4f>
    getLightInfo(const Vec3f& targetPos) const noexcept = 0;

    virtual ~Light() = default;
protected:
    Color4f mLightColor;
};

class ParallelLight final : public Light {
public:
    ParallelLight(const Vec3f& lightDir, const Color4f& lightColor)
        : mLightDir(lightDir), Light(lightColor) {}
    std::pair<Vec3f, Color4f> getLightInfo(const Vec3f& targetPos) const noexcept override {
        return {mLightDir, Light::mLightColor};
    }
private:
    Vec3f mLightDir;
};

class PointLight final : public Light {
public:
    PointLight(const Vec3f& lightPos, const Color4f& lightColor, 
               float fallOffStart = 5.0f, float fallOffEnd = 100.0f)
        : mLightPos(lightPos), Light(lightColor), 
          mFallOffStart(fallOffStart), mFallOffEnd(fallOffEnd) {}
    
    std::pair<Vec3f, Color4f> getLightInfo(const Vec3f& targetPos) const noexcept override {
        Vec3f lightDirVec = mLightPos - targetPos;
        float distance = lightDirVec.length();
        float attenuation = clamp(0.0f, 1.0f, (mFallOffEnd - distance) / (mFallOffEnd - mFallOffStart));
        return {lightDirVec / distance, attenuation * Light::mLightColor};
    }
private:
    Vec3f mLightPos;
    float mFallOffStart;
    float mFallOffEnd;
};

class SpotLight final : public Light {
public:
    SpotLight(const Vec3f& lightPos, const Color4f& lightColor, const Vec3f& mainLightDir,
              float fallOffStart = 5.0f, float fallOffEnd = 100.0f, 
              float power = 8)
        : mLightPos(lightPos), Light(lightColor), mMainLightDir(mainLightDir),
          mFallOffStart(fallOffStart), mFallOffEnd(fallOffEnd), 
          mPower(power) {}

    std::pair<Vec3f, Color4f> getLightInfo(const Vec3f& targetPos) const noexcept override {
        Vec3f lightDirVec = targetPos - mLightPos;
        float distance = lightDirVec.length();
        lightDirVec = lightDirVec / distance;
        float attenuation = clamp(0.0f, 1.0f, (mFallOffEnd - distance) / (mFallOffEnd - mFallOffStart));
        float kSpot = std::powf(std::max(lightDirVec * mMainLightDir, 0.0f), mPower);
        return {lightDirVec, attenuation * kSpot * Light::mLightColor};
    }
private:
    Vec3f mMainLightDir;
    Vec3f mLightPos;
    float mFallOffStart;
    float mFallOffEnd;
    float mPower;
};

#endif